<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Building a router</title>
  <script type="text/javascript" src='tmpl.js'></script>
  <script>
    // 参考：
    // http://joakim.beng.se/blog/posts/a-javascript-router-in-20-lines.html
    // https://gist.github.com/joakimbeng/7918297
    var routes = {}
    // An array of the current route's events:
    var events = []
    var el = null
    // Context functions shared between all controllers:
    var ctx = {
      on: function (selector, evt, handler) {
        events.push([selector, evt, handler])
      },
      refresh: function (listeners) {
        listeners.forEach(function (fn) { fn() })
      }
    }
      // The element where the routes are rendered:
    function route (path, templateId, controller) {
      if (typeof templateId === 'function') {
        controller = templateId
        templateId = null
      }
      var listeners = []
      Object.defineProperty(controller.prototype, '$on', {value: ctx.on})
      Object.defineProperty(controller.prototype, '$refresh', {value: ctx.refresh.bind(undefined, listeners)})
      routes[path] = {templateId: templateId, controller: controller, onRefresh: listeners.push.bind(listeners)}
    }
    function forEachEventElement(fnName) {
      for (var i = 0, len = events.length; i < len; i++) {
        var els = el.querySelectorAll(events[i][0])
        for (var j = 0, elsLen = els.length; j < elsLen; j++) {
          els[j][fnName].apply(els[j], events[i].slice(1))
        }
      }
    }
    function addEventListeners() {
      forEachEventElement('addEventListener');
    }
    function removeEventListeners() {
      forEachEventElement('removeEventListener');
    }
    function router () {
      el = el || document.getElementById('view')
      // Remove current event listeners:
      removeEventListeners()
      // Clear events, to prepare for next render:
      events = [];
      var url = location.hash.slice(1) || '/'
      // Get route by url or fallback if it does not exist:
      var route = routes[url] || routes['*']
      if (el && route.controller) {
        // el.innerHTML = tmpl(route.templateId, new route.controller())
        var ctrl = new route.controller()
        if (!el || !route.templateId) {
          // If there's nothing to render, abort:
          return
        }
        // Listen on route refreshes:
        route.onRefresh(function () {
          removeEventListeners()
          // Render route template with John Resig's template engine:
          el.innerHTML = tmpl(route.templateId, ctrl)
          addEventListeners()
        })
        // Trigger the first refresh:
        ctrl.$refresh()
      }
    }
    window.addEventListener('hashchange', router)
    window.addEventListener('load', router)
    // Expose the route register function:
    this.route = route
  </script>
  <script type="text/html" id="home">
    <h1>Router FTW!</h1>
  </script>
  <script type="text/html" id="template1">
    <h1>Page 1: <%= greeting %></h1>
    <p><%= moreText %></p>
    <button class="my-button">Click me <%= counter %></button>
  </script>
  <script type="text/html" id="template2">
    <h1>Page 2: <%= heading %></h1>
    <p>Lorem ipsum...</p>
  </script>
  <script type="text/html" id="error404">
    <h1>404 Not found</h1>
  </script>
</head>
<body>
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#/page1">Page 1</a></li>
    <li><a href="#/page2">Page 2</a></li>
  </ul>
  <div id="view"></div>
  <script>
    route('/', 'home', function () {})
    route('/page1', 'template1', function () {
      this.greeting = 'Hello world!'
      this.moreText = 'Bacon ipsum...'
      this.counter = 0
      this.$on('.my-button', 'click', function () {
        this.counter += 1
        this.$refresh()
      }.bind(this))
    })
    route('/page2', 'template2', function () {
      this.heading = 'I\'m page two!'
    })
    route('*', 'error404', function () {})
  </script>
</body>
</html>
